#include <prism/backend/generation/llvm/llvm-main.hpp>
//=====================================================================
#include <sstream>
//=====================================================================
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
//=====================================================================
#include <atma/string_builder.hpp>
//=====================================================================
#include <sooty/frontend/syntactic_analysis/algorithm.hpp>
//=====================================================================
#include <prism/frontend/syntactical_analysis/marshall.hpp>
#include <prism/frontend/syntactical_analysis/parse.hpp>
#include <prism/frontend/semantic_analysis/decorate.hpp>
#include <prism/frontend/semantic_analysis/semantic_analysis.hpp>
#include <prism/frontend/semantic_analysis/resolve/identifier.hpp>
#include <prism/frontend/semantic_analysis/resolve/type.hpp>
#include <prism/backend/generation.hpp>




//=====================================================================
// using and abusing
//=====================================================================
using namespace prism;




//=====================================================================
// begin!
//=====================================================================
void prism::llvm_prepass( sooty::parseme_ptr_ref N )
{
	int tmpvar = 0;
	sooty::depth_first_for_each(N, add_temporary_information_to_expressions(tmpvar), false);
}

void prism::generate_llvm(std::ostream& out_stream, sooty::const_parseme_ptr_ref N)
{
	llvm::detail::outstream() = &out_stream;
	llvm::module(N);
}


void llvm::module(sooty::const_parseme_ptr_ref N)
{
	for (sooty::parseme_container::const_iterator i = N->children.begin(); i != N->children.end(); ++i)
	{
		if ((*i)->id == ID::function)
			function(*i);
	}
}

void llvm::function(sooty::const_parseme_ptr_ref N)
{
	sooty::const_parseme_ptr_ref return_type = marshall::function::return_type(N);
	sooty::const_parseme_ptr_ref name = marshall::function::name(N);
	sooty::const_parseme_ptr_ref parameter_list_node = marshall::function::parameter_list(N);
	sooty::const_parseme_ptr_ref body = marshall::function::body(N);

	output_stream() << "define " << decorated_type(return_type) << " @" << decorate_function(N);
	parameter_list(parameter_list_node);

	output_stream() << "{\n";
	block(body, "entry");
	output_stream() << "}\n";
}

void llvm::parameter_list(sooty::const_parseme_ptr_ref N)
{
	output_stream() << "(";
	if (!N->children.empty())
	{
		sooty::parseme_container::const_iterator i = N->children.begin();
		parameter(*i++);
		for ( ; i != N->children.end(); ++i)
		{
			output_stream() << ", ";
			parameter(*i);
		}
	}
	output_stream() << ")" << std::endl;
}

void llvm::parameter(sooty::const_parseme_ptr_ref N)
{
	output_stream() << logical_type_name(N)
		<< " " << rvalue_name(N) << "0"; //marshall::variable_definition::name(N)->value.string;
}

void llvm::block(sooty::const_parseme_ptr_ref N, const std::string& name)
{
	output_stream() << N->value.string << ":\n";
	add_tab();
	std::for_each(N->children.begin(), N->children.end(), statement);
	sub_tab();
}

bool is_block(sooty::const_parseme_ptr_ref N)
{
	return N->id == ID::block;
}

void prism::llvm::phi_node( sooty::const_parseme_ptr_ref N, const std::string& block1, const std::string& block2 )
{
	output_stream() 
		<< rvalue_name(N->children[0]) << " = phi " << logical_type_name(N->children[0]);
	
	for (sooty::parseme_container::iterator i = N->children.begin() + 1; i != N->children.end(); ++i)
	{
		output_stream(false)
			<< " [" << rvalue_name(N->children[1]) << ", %" << sooty::direct_upwards_find_first_if(*i, is_block)->value.string << "]";
		if (i + 1 != N->children.end())
			output_stream(false) << ",";
	}
			
	output_stream() << std::endl;
}

bool is_return_statement(sooty::const_parseme_ptr_ref N)
{
	return N->id == ID::return_statement;
}

void llvm::statement(sooty::const_parseme_ptr_ref N)
{
	switch (N->id)
	{
		case ID::variable_definition:
		{
			sooty::parseme_ptr type = resolve::type_of_pred(marshall::variable_definition::type(N))();
			sooty::const_parseme_ptr_ref name = marshall::variable_definition::name(N);
			
			output_stream()
				<< "%" << name->value.string << " = alloca " << logical_type_name(type) << "\n";
			
			if ( marshall::variable_definition::has_defvalue(N) )
			{
				sooty::const_parseme_ptr_ref defvalue = marshall::variable_definition::defvalue(N);
				access_expression(defvalue);
				
				output_stream()
					<< "store " << logical_type_name(defvalue) << " " << rvalue_name(defvalue)
					<< ", " << storage_type_name(N) << " " << lvalue_name(N) << "\n";
			}
			
			output_stream() << "%" << name->value.string << "0" //N->value.string //<< "." << N->value.integer
				<< " = load " << logical_type_name(N) << "* " << lvalue_name(N) << "\n";
			
			break;
		}
		
		case ID::return_statement:
		{
			if (!N->children.empty()) {
				access_expression(N->children[0]);
				output_stream() << "ret " + logical_type_name(N->children[0]) + " " << rvalue_name(N->children[0]) << '\n';
			}
			else {
				output_stream() << "ret void\n";
			}
			
			//output_stream()
			//	<< "%tmp = fptoui float %cc1 to i32\n"
			//	<< "ret i32 %tmp\n";
			
			break;
		}
	
		case ID::if_statement:
		{
			access_expression(N->children[0]);
			
			std::string true_block_name = atma::string_builder("if_")(N->position.stream)("_true");
			std::string false_block_name = atma::string_builder("if_")(N->position.stream)("_false");
			std::string cont_block_name = atma::string_builder("if_")(N->position.stream)("_cont");
			
			output_stream() << "br i1 " << rvalue_name(N->children[0]) << ", label %" << true_block_name << ", label %" << false_block_name << "\n";
			
			block(N->children[1], true_block_name);
			bool true_found = false;
			for (sooty::parseme_container::iterator i = N->children[1]->children.begin(); i != N->children[1]->children.end(); ++i) {
				if ( (*i)->id == ID::return_statement ) {
					true_found = true;
				}
			}
			if (!true_found)
				output_stream() << "\tbr label %" << cont_block_name << "\n";
			
			block(N->children[2], false_block_name);
			bool false_found = false;
			for (sooty::parseme_container::iterator i = N->children[2]->children.begin(); i != N->children[2]->children.end(); ++i) {
				if ( (*i)->id == ID::return_statement ) {
					false_found = true;
				}
			}
			if (!false_found)
				output_stream() << "\tbr label %" << cont_block_name << "\n";
			
			
			if (!false_found || !true_found)
				output_stream() << cont_block_name << ":\n";
			
			llvm::add_tab();
			std::for_each(N->children[3]->children.begin(), N->children[3]->children.end(), boost::bind(phi_node, _1, true_block_name, false_block_name));
			llvm::sub_tab();
			break;
		}
		
		case ID::delete_:
		{
			output_stream()
				<< "free " << storage_type_name(N->children[0]) << " " << lvalue_name(N->children[0]) << "\n";
			break;
		}
		
		case ID::assignment:
			access_expression(N);
			break;
	}
	
	output_stream(false) << std::flush;
}

void llvm::argument_list( sooty::const_parseme_ptr_ref N )
{
	output_stream(false) << "(";
	if (!N->children.empty())
	{
		sooty::parseme_container::const_iterator i = N->children.begin();
		argument(*i++);
		for ( ; i != N->children.end(); ++i)
		{
			output_stream(false) << ", ";
			argument(*i);
		}
	}
	output_stream(false) << ")" << std::endl;
}

void llvm::argument(sooty::const_parseme_ptr_ref N)
{
	sooty::const_parseme_ptr_ref type = marshall::argument::type(N);
	sooty::const_parseme_ptr_ref name = marshall::argument::name(N);
	output_stream(false) << logical_type_name(resolve::type_of_pred(type)()) << " " << rvalue_name(name);
}






// there'll be one node appended which will have children containing relevant information
//const size_t llvminfoid = 20000;
void prism::add_temporary_information_to_expressions::operator()( sooty::parseme_ptr_ref N )
{
	switch (N->id)
	{
		case ID::function:
		{
			tmpvar = 0;
			break;
		}
		
		case ID::add:
		case ID::sub:
		case ID::mul:
		case ID::div:
		case ID::new_:
		case ID::dereference:
		case ID::equ:
		case ID::function_call:
		{
			if (N->semantics.empty())
				N->semantics.push_back( sooty::semant_t(tmpvar++) );
			break;
		}
	}
}


